#include <windows.h>
#include <gdiplus.h>
#include "ocl.h"

bool use_image2d = false;
bool use_normalized = false;
bool use_host_ptr = false;
bool use_cpu = false;

using namespace Gdiplus;
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
    UINT  num = 0;          // number of image encoders
    UINT  size = 0;         // size of the image encoder array in bytes

    ImageCodecInfo* pImageCodecInfo = NULL;

    GetImageEncodersSize(&num, &size);
    if(size == 0)
        return -1;  // Failure

    pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if(pImageCodecInfo == NULL)
        return -1;  // Failure

    GetImageEncoders(num, size, pImageCodecInfo);

    for(UINT j = 0; j < num; ++j)
    {
        if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
        {
            *pClsid = pImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return j;  // Success
        }    
    }

    free(pImageCodecInfo);
    return -1;  // Failure
}

#define COMPLETE_COMMAND clFlush(dev.Get_cl_command_queue());\
	clFinish(dev.Get_cl_command_queue());\

void runOcl(char* scsPath)
{
	DWORD timeProfile = 0;
	printf ("(image2d, normalized, use_host_ptr, use_cpu)=(%i, %i, %i, %i)\n", 
            (int)use_image2d, (int)use_normalized, (int)use_host_ptr, (int)use_cpu);

    ULONG_PTR    gdiplusToken;
    GdiplusStartupInput gdiplusStartupInput;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    
    WCHAR wcsPath[1024] = {0};
    mbstowcs(wcsPath, scsPath, strlen(scsPath));
    Bitmap imgSrc(wcsPath);

    unsigned int imgW = imgSrc.GetWidth();
    unsigned int imgH = imgSrc.GetHeight();

    BitmapData bmpData;
    Rect  rc(0, 0, imgW, imgH);

    imgSrc.LockBits(&rc, ImageLockModeRead, PixelFormat32bppARGB, &bmpData);	

    char*        imgData = (char*)bmpData.Scan0;
    unsigned int imgS = bmpData.Stride;


    char* rawData = imgData;
    unsigned int dataLen = imgS * imgH;

    using namespace ocl;

	const char* progStr;
    
	if (use_image2d && use_normalized){
		const char str[] = "constant	sampler_t	sample_nearest	=	CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE   | CLK_FILTER_NEAREST;\n"
		"__kernel void addr(read_only image2d_t src, read_only image2d_t eff, write_only image2d_t dst)"
		"{"
		"	 int x = get_global_id(0);	"
		"	 int y = get_global_id(1);	"
		"    int2	i2Coord = (int2)(x, y);	"
		"	 float4  f4_src = read_imagef( src, sample_nearest, i2Coord );	"
		"	 float4  f4_eff = read_imagef( eff, sample_nearest, i2Coord );	"
		"	 write_imagef( dst, i2Coord, f4_src + f4_eff);	"
		"}";
		progStr = str;
	}
	else if (use_image2d && !use_normalized){
		const char str[] = "constant	sampler_t	sample_nearest	=	CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE   | CLK_FILTER_NEAREST;\n"
		"__kernel void addr(read_only image2d_t src, read_only image2d_t eff, write_only image2d_t dst)"
		"{"
		"	 int x = get_global_id(0);	"
		"	 int y = get_global_id(1);	"
		"    int2	i2Coord = (int2)(x, y);	"
		"	 uint4  color = read_imageui( src, sample_nearest, i2Coord );	"
		"	 uint4  effi = read_imageui( eff, sample_nearest, i2Coord );	"
		"	 write_imageui( dst, i2Coord, color + effi );	"
		"}";
		progStr = str;
	}
	else{
		const char str[] = "__kernel void addr(__global const int* src, __global const int* eff, __global int* dst)"
		"{"
		"    int idx = get_global_id(0);"
		"    dst[idx] = src[idx] + eff[idx];"
		"}";
		progStr = str;
	}
    
    oclDevice&  dev = oclSelDevice(use_cpu? CL_DEVICE_TYPE_CPU :CL_DEVICE_TYPE_GPU);
    if (!dev.Ready())
    {
        fprintf(stderr, "[ERROR] Not a valid OpenCL Device!!!\n");
        return;
    }

    oclProgram  prg(dev, progStr);
    oclKernel&  krl = prg["addr"];

    int* src = (int*)rawData;
    int  len = dataLen/sizeof(int);
    int* dst = new int[len];
    int* eff = new int[len];
    for(int i = 0; i < len; ++i)
    {
        eff[i] = 0xffffffff;
    }

	cl_image_format	fmt = {CL_RGBA, CL_UNSIGNED_INT8};
	if (use_normalized)
		fmt.image_channel_data_type = CL_UNORM_INT8;

	cl_mem Smem = 0;
	cl_mem Emem = 0;
	cl_mem Dmem = 0;

	if (use_host_ptr){
		if (use_image2d){
			Smem = oclImg::create( src, imgW, imgH, imgS, CL_MEM_READ_ONLY  | CL_MEM_USE_HOST_PTR, &fmt );
			Emem = oclImg::create( eff, imgW, imgH, imgS, CL_MEM_READ_ONLY  | CL_MEM_USE_HOST_PTR, &fmt );
			Dmem = oclImg::create( dst, imgW, imgH, imgS, CL_MEM_WRITE_ONLY | CL_MEM_USE_HOST_PTR, &fmt );
		}else{
			Smem = oclBuf::create(     src, len, CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR  );
			Emem = oclBuf::create(     eff, len, CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR  );
            Dmem = oclBuf::create( (int*)0, len, CL_MEM_WRITE_ONLY| CL_MEM_ALLOC_HOST_PTR);
		}
	}else{
		if (use_image2d){
			Smem = oclImg::create( src, imgW, imgH, imgS, CL_MEM_READ_ONLY  | CL_MEM_COPY_HOST_PTR, &fmt );
			Emem = oclImg::create( eff, imgW, imgH, imgS, CL_MEM_READ_ONLY  | CL_MEM_COPY_HOST_PTR, &fmt );
			Dmem = oclImg::create( dst, imgW, imgH, imgS, CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR, &fmt );
		}else{
			Smem = oclBuf::R( src, len );
			Emem = oclBuf::R( eff, len );
			Dmem = oclBuf::W( dst, len );
		}
	}

	if (use_image2d){
		size_t szGlobal[] = {imgW, imgH};
		size_t szLocal[]  = {1, 1};
		krl.SetDimensionAndSize(2, szGlobal, szLocal);
	}else{
		size_t szGlobal[] = {len};
		size_t szLocal[]  = {1};
		krl.SetDimensionAndSize(1, szGlobal, szLocal);
	}

	krl.SetArg(0, &Smem);
    krl.SetArg(1, &Emem);
    krl.SetArg(2, &Dmem);
    
	COMPLETE_COMMAND
	timeProfile = timeGetTime();

    krl.Run(true);
    
	printf("Kernel profile = %i(ms)\n", timeGetTime() - timeProfile);

    void* output = 0;
	COMPLETE_COMMAND
	timeProfile = timeGetTime();

	if (use_host_ptr){
		if (use_image2d)
			oclImg::map(Dmem, output, CL_MAP_READ, 0, 0, imgW, imgH);
		else
			oclBuf::map(Dmem, output, CL_MAP_READ, 0, dataLen);
	}else{
		if (use_image2d)
			oclImg::read(Dmem, dst, 0, 0, imgW, imgH);
		else
			oclBuf::read(Dmem, dst, len);
		
		output = dst;
	}

	printf("Readback profile = %i(ms)\n", timeGetTime() - timeProfile);

    CLSID   encoderClsid;
    GetEncoderClsid(L"image/png", &encoderClsid);
    Bitmap imgDst(imgW, imgH, imgS, PixelFormat32bppARGB, (BYTE*)output);
    imgDst.Save(L"target.png", &encoderClsid, NULL);

	if (use_host_ptr)
		oclMem::unmap(Dmem, output);

    oclReleaseMemObject(Smem);
    oclReleaseMemObject(Emem);
    oclReleaseMemObject(Dmem);
    delete[] eff;
    delete[] dst;
    imgSrc.UnlockBits(&bmpData);

	printf("\n");
}

int main(int argc, char** argv)
{
    if (argc < 2)
    {
        printf("Usage: %s [OPTION]... -i [FILE]...\n\n", argv[0]);
		printf("-image2d        use image2d_t\n");
		printf("-normalized     use normailized image2d_t\n");
		printf("-use_host_ptr   use host memory\n");
        printf("-cpu            use cpu\n");
        printf("ex: cmd> image_win.exe -i ocl.jpg -cpu -image2d -use_host_ptr, -normalized\n");
        return -1;
    }
	
	char* input = 0;	
	
	for (int arg=1; arg < argc;) {
		char* opt = argv[arg++];
		if (_stricmp(opt, "-image2d") == 0){
			use_image2d = true;
			continue;
		}
		if (_stricmp(opt, "-normalized") == 0){
			use_normalized = true;
			continue;
		}
		if (_stricmp(opt, "-host_ptr") == 0){
			use_host_ptr = true;
			continue;
		}
		if (_stricmp(opt, "-i") == 0){
			input = argv[arg++];
			continue;
		}
        if (_stricmp(opt, "-cpu") == 0){
            use_cpu = true;
            continue;
        }
	}

	if (!input)
	{
		printf("There is no input file\n");
		return 0;
	}
    
	
	runOcl(input);
	
    return 0;
}
